#include "pipeconn.h"
#include <iprt/string.h>
#include <VBox/err.h>


//#define _DEBUG_
#ifdef _DEBUG_
#define TRACE(format, ...)                                                                                            \
	{                                                                                                                 \
		char buffer[256] = {0};                                                                                       \
		SYSTEMTIME ct;                                                                                                \
		GetLocalTime(&ct);                                                                                            \
		sprintf(buffer, "%d:%d:%d.%d:   " format, ct.wHour, ct.wMinute, ct.wSecond, ct.wMilliseconds, ##__VA_ARGS__); \
		OutputDebugStringA(buffer);                                                                                   \
	}
#else
#define TRACE(format, ...)
#endif

HANDLE pipeConnect(const char *pipeNameFormat, const char *prefix)
{
	HANDLE pipe = INVALID_HANDLE_VALUE;
	char pipeName[256];
	//sprintf(pipeName, "\\\\.\\pipe\\%s_pipe", prefix);
	RTStrPrintf(pipeName, sizeof(pipeName), pipeNameFormat, prefix);

	//TRACE("pipeConnect %s start", pipeName);

	/* We're going to loop in order to wait for the pipe server to
	 * be setup properly.
	 */
	int tries = 10;
	for (; tries > 0; tries--)
	{
		pipe = CreateFile(
			pipeName,					  // pipe name
			GENERIC_READ | GENERIC_WRITE, // read & write
			0,							  // no sharing
			NULL,						  // default security attributes
			OPEN_ALWAYS,				  // open always
			FILE_FLAG_OVERLAPPED,		  // default attributes
			NULL);						  // no template file

		/* If we have a valid pipe handle, break from the loop */
		if (pipe != INVALID_HANDLE_VALUE)
			break;

		// Exit if an error other than ERROR_PIPE_BUSY occurs.

		if (GetLastError() != ERROR_PIPE_BUSY)
		{
			TRACE("%s: CreateFile failed: %d\n", __FUNCTION__, (int)GetLastError());
			errno = EINVAL;
			return INVALID_HANDLE_VALUE;
		}

		/* Wait for 5 seconds */
		if (!WaitNamedPipe(pipeName, 5000))
		{
			TRACE("%s: WaitNamedPipe time out %d : %d\n", __FUNCTION__, tries, (int)GetLastError());
			errno = EINVAL;
			continue;
		}
	}
	return pipe;
}


int pipeWrite(HANDLE hPipe, void *buf, unsigned int size)
{
	DWORD written = 0;
	OVERLAPPED ol = {0};
	if (!WriteFile(hPipe, (const char *)buf, size, &written, &ol))
	{
		if (GetLastError() == ERROR_IO_PENDING)
		{
			WaitForSingleObject(hPipe, INFINITE);
			GetOverlappedResult(hPipe, &ol, &written, false);
			return (int)written;
		}
		return -1;
	}

	return (int)written;
}

int pipeWrite(HANDLE hPipe, void *buf, unsigned int *size, OVERLAPPED *ol)
{
	DWORD written = 0;
	DWORD rc = 0;
	int error = 0;
	unsigned int len = *size;
	ol->Offset = 0;
	ol->OffsetHigh = 0;

	if (!WriteFile(hPipe, (const char *)buf, len, &written, ol))
	{
		rc = GetLastError();
		if (rc == ERROR_IO_PENDING)
		{
			rc = 0;
			if (!GetOverlappedResult(hPipe, ol, &written, TRUE))
				rc = GetLastError();
		}
		error = RTErrConvertFromWin32(rc);		
		if ( error == VERR_BROKEN_PIPE || error == VERR_PIPE_NOT_CONNECTED )
		{
			TRACE("pipeConnect WriteFile returned (%d)\n", error);
			FlushFileBuffers(hPipe);
			DisconnectNamedPipe(hPipe);
			CloseHandle(hPipe);
			*size = 0;
			return error;
		}
	}
	*size = (unsigned int)written;
	return VINF_SUCCESS;
}

//  If the hEvent member of the OVERLAPPED structure is NULL,
//  the system uses the state of the hFile handle to signal
//  when the operation has been completed. Use of file,
//  named pipe, or communications-device handles for this purpose is discouraged.
//  It is safer to use an event object because of the confusion
//  that can occur when multiple simultaneous overlapped operations are performed on the same file,
//  named pipe, or communications device. In this situation,
//  there is no way to know which operation caused the object's state to be signaled.

//OVERLAPPED
int pipeRead(HANDLE hPipe, void *buf, unsigned int len)
{
	DWORD readcount = 0;
	OVERLAPPED ol = {0};
	int error = 0;

	if (!ReadFile(hPipe, (char *)buf, len, &readcount, &ol) || readcount == 0)
	{
		if (GetLastError() == ERROR_IO_PENDING)
		{
			WaitForSingleObject(hPipe, INFINITE);
			GetOverlappedResult(hPipe, &ol, &readcount, false);
			TRACE("pipeConnect Overlapped readcount (%d)\n", (int)readcount);

			error = RTErrConvertFromWin32(GetLastError());
			if (error != -VERR_EOF && error != -VERR_BROKEN_PIPE && error != -VERR_PIPE_NOT_CONNECTED)
			{
				return (int)readcount;
			}
			return (int)readcount;
		}
		return -1;
	}
	TRACE("pipeConnect readcount(%d)\n", (int)readcount);
	return (int)readcount;
}

int pipeRead(HANDLE hPipe, void *buf, unsigned int *len, OVERLAPPED *ol)
{
	DWORD readcount = 0;
	DWORD rc = 0;
	int error = 0;
	ol->Offset = 0;
	ol->OffsetHigh = 0;

	if (!ReadFile(hPipe, (char *)buf, PIPE_BUFFER_LEN, &readcount, ol) || readcount == 0)
	{
		rc = GetLastError();
		if (rc == ERROR_IO_PENDING)
		{
			rc = 0;
			if (!GetOverlappedResult(hPipe, ol, &readcount, TRUE))
				rc = GetLastError();
		}
		error = RTErrConvertFromWin32(rc);		
		if ( error == VERR_EOF || error == VERR_BROKEN_PIPE || error == VERR_PIPE_NOT_CONNECTED )
		{
			TRACE("pipeConnect ReadFile returned (%d)\n", error);
			*len = 0;
			return error;
		}
		// error = RTErrConvertFromWin32(rc);  //VERR_EOF(-110) VERR_BROKEN_PIPE(-301) VERR_PIPE_NOT_CONNECTED(-305)
		// if (error != -VERR_EOF && error != -VERR_BROKEN_PIPE && error != -VERR_PIPE_NOT_CONNECTED) // VERR_EOF VERR_BROKEN_PIPE VERR_PIPE_NOT_CONNECTED
		// {
		// 	return (int)readcount;
		// }
		//return -1;

	}
	*len = (unsigned int)readcount;
	return VINF_SUCCESS;
}

void pipeClose(HANDLE hPipe)
{
	if (hPipe != INVALID_HANDLE_VALUE)
	{
		DisconnectNamedPipe(hPipe);
		CloseHandle(hPipe);
	}
}

void InitPipes(HANDLE pipes[], int maxNum)
{
	for (int i = 0; i < maxNum; i++)
		pipes[i] = INVALID_HANDLE_VALUE;
}

int TakeOneSlot(HANDLE pipes[], int maxNum, HANDLE hPipe)
{
	int slot = -1;
	//todo: add lock
	for (int i = 0; i < maxNum; i++)
	{
		if (INVALID_HANDLE_VALUE == pipes[i])
		{
			pipes[i] = hPipe;
			slot = i;
			break;
		}
	}
	return slot;
}

void ReturnOneSlot(HANDLE pipes[], int maxNum, int slot)
{
	//todo: add lock
	if (0 <= slot && slot < maxNum)
	{
		pipeClose(pipes[slot]);
		pipes[slot] = INVALID_HANDLE_VALUE;
	}
}

HANDLE GetPipeHandleAt(HANDLE pipes[], int maxNum, int slot)
{
	if (0 <= slot && slot < maxNum)
	{
		return pipes[slot];
	}
	else
	{
		return INVALID_HANDLE_VALUE;
	}
}
